{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bcbe5c87",
   "metadata": {},
   "source": [
    "# OpenAI Tools\n",
    "\n",
    "These output parsers extract tool calls from OpenAI's function calling API responses. This means they are only usable with models that support function calling, and specifically the latest `tools` and `tool_choice` parameters. We recommend familiarizing yourself with [function calling](/docs/modules/model_io/chat/function_calling) before reading this guide.\n",
    "\n",
    "There are a few different variants of output parsers:\n",
    "\n",
    "- [JsonOutputToolsParser](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.openai_tools.JsonOutputToolsParser.html#langchain.output_parsers.openai_tools.JsonOutputToolsParser): Returns the arguments of the function call as JSON\n",
    "- [JsonOutputKeyToolsParser](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.openai_tools.JsonOutputKeyToolsParser.html#langchain.output_parsers.openai_tools.JsonOutputKeyToolsParser): Returns the value of specific key in the function call as JSON\n",
    "- [PydanticToolsParser](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.openai_tools.PydanticToolsParser.html#langchain.output_parsers.openai_tools.PydanticToolsParser): Returns the arguments of the function call as a Pydantic Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "aac4262b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.pydantic_v1 import BaseModel, Field, validator\n",
    "from langchain_openai import ChatOpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "52cb351d",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Joke(BaseModel):\n",
    "    \"\"\"Joke to tell user.\"\"\"\n",
    "\n",
    "    setup: str = Field(description=\"question to set up a joke\")\n",
    "    punchline: str = Field(description=\"answer to resolve the joke\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2c3259c4",
   "metadata": {},
   "outputs": [],
   "source": [
    "model = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0).bind_tools([Joke])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "75c33a76-ead8-43aa-ba18-c1822c38cfa9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'type': 'function',\n",
       "  'function': {'name': 'Joke',\n",
       "   'description': 'Joke to tell user.',\n",
       "   'parameters': {'type': 'object',\n",
       "    'properties': {'setup': {'description': 'question to set up a joke',\n",
       "      'type': 'string'},\n",
       "     'punchline': {'description': 'answer to resolve the joke',\n",
       "      'type': 'string'}},\n",
       "    'required': ['setup', 'punchline']}}}]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.kwargs[\"tools\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d3e9007c",
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [(\"system\", \"You are helpful assistant\"), (\"user\", \"{input}\")]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87680951",
   "metadata": {},
   "source": [
    "## JsonOutputToolsParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cb065bdd",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.output_parsers.openai_tools import JsonOutputToolsParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "6ff758c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "parser = JsonOutputToolsParser()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "27a3acd1",
   "metadata": {},
   "outputs": [],
   "source": [
    "chain = prompt | model | parser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "59b59179",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'type': 'Joke',\n",
       "  'args': {'setup': \"Why don't scientists trust atoms?\",\n",
       "   'punchline': 'Because they make up everything!'}}]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.invoke({\"input\": \"tell me a joke\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f093b2b-ffd1-47b7-9221-b4265ae52701",
   "metadata": {},
   "source": [
    "To include the tool call id we can specify `return_id=True`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "d43fd620-dcdc-4ad0-a3a9-e7d2d71d6e68",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'type': 'Joke',\n",
       "  'args': {'setup': \"Why don't scientists trust atoms?\",\n",
       "   'punchline': 'Because they make up everything!'},\n",
       "  'id': 'call_Isuoh0RTeQzzOKGg5QlQ7UqI'}]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "parser = JsonOutputToolsParser(return_id=True)\n",
    "chain = prompt | model | parser\n",
    "chain.invoke({\"input\": \"tell me a joke\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ca55ac9",
   "metadata": {},
   "source": [
    "## JsonOutputKeyToolsParser\n",
    "\n",
    "This merely extracts a single key from the returned response. This is useful for when you are passing in a single tool and just want it's arguments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "f8bc404e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List\n",
    "\n",
    "from langchain.output_parsers.openai_tools import JsonOutputKeyToolsParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "c91c5949",
   "metadata": {},
   "outputs": [],
   "source": [
    "parser = JsonOutputKeyToolsParser(key_name=\"Joke\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "b4583baf",
   "metadata": {},
   "outputs": [],
   "source": [
    "chain = prompt | model | parser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "e8b766ff",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'setup': \"Why don't scientists trust atoms?\",\n",
       "  'punchline': 'Because they make up everything!'}]"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.invoke({\"input\": \"tell me a joke\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc5695c5-451f-482f-bde6-462d85f1a93e",
   "metadata": {},
   "source": [
    "Certain models can return multiple tool invocations each call, so by default the output is a list. If we just want to return the first tool invocation, we can specify `return_single=True`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "b1f3097a-5040-435e-9e26-bbdf9506aead",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'setup': \"Why don't scientists trust atoms?\",\n",
       " 'punchline': 'Because they make up everything!'}"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "parser = JsonOutputKeyToolsParser(key_name=\"Joke\", return_single=True)\n",
    "chain = prompt | model | parser\n",
    "chain.invoke({\"input\": \"tell me a joke\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "941a3d4e",
   "metadata": {},
   "source": [
    "## PydanticToolsParser\n",
    "\n",
    "This builds on top of `JsonOutputToolsParser` but passes the results to a Pydantic Model. This allows for further validation should you choose."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "f51823fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.output_parsers.openai_tools import PydanticToolsParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "3c6a5e4d",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Joke(BaseModel):\n",
    "    \"\"\"Joke to tell user.\"\"\"\n",
    "\n",
    "    setup: str = Field(description=\"question to set up a joke\")\n",
    "    punchline: str = Field(description=\"answer to resolve the joke\")\n",
    "\n",
    "    # You can add custom validation logic easily with Pydantic.\n",
    "    @validator(\"setup\")\n",
    "    def question_ends_with_question_mark(cls, field):\n",
    "        if field[-1] != \"?\":\n",
    "            raise ValueError(\"Badly formed question!\")\n",
    "        return field\n",
    "\n",
    "\n",
    "parser = PydanticToolsParser(tools=[Joke])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "d2bbd54f",
   "metadata": {},
   "outputs": [],
   "source": [
    "model = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0).bind_tools([Joke])\n",
    "chain = prompt | model | parser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "db1a06e8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Joke(setup=\"Why don't scientists trust atoms?\", punchline='Because they make up everything!')]"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.invoke({\"input\": \"tell me a joke\"})"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
